/*
 * Su-yun Information Technology, Co.,Ltd CONFIDENTIAL
 *
 * (C) Copyright Su-yun Information Technology, Co.,Ltd. 2021 All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains the property of
 * Su-yun Information Technology, Co.,Ltd. The intellectual property herein
 * are protected by trade secret, patent law or copyright law. Dissemination
 * of this information or reproduction of this material is strictly forbidden
 * unless prior written permission is obtained from Su-yun Information Technology.
 *
 * https://suyunplat.com/
 *
 */
package com.xtiim.battery.discharge.util;

import lombok.extern.slf4j.Slf4j;
import purejavacomm.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

@Slf4j
public class SerialPortUtil {

    /**
     * find available com port
     *
     * @return port list
     */
    public static ArrayList<String> findPort() {
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        ArrayList<String> portNameList = new ArrayList<>();
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }

    /**
     * Open com port
     *
     * @param portName
     * @param baudrate
     * @param databits
     * @param parity
     * @param stopbits
     * @return
     */
    public static SerialPort openPort(String portName, int baudrate, int databits, int parity, int stopbits) {
        try {
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
            log.info("==========================port open: {}=========================", portName);
            CommPort commPort = portIdentifier.open(portName, 2000);
            if (!(commPort instanceof SerialPort)) {
                log.error("It is NOT com port.");
                return null;
            }

            SerialPort serialPort = (SerialPort) commPort;
            serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);
            log.debug("Open {} success.", portName);
            return serialPort;
        } catch (NoSuchPortException e1) {
            log.error("Find no port.");
        } catch (PortInUseException e2) {
            log.error("Port is occupied.");
        } catch (UnsupportedCommOperationException e) {
            log.error("Comm operation is not supported.");
        }

        return null;
    }

    /**
     * Close com port
     *
     * @param serialPort
     */
    public static void closePort(SerialPort serialPort) {
        if (serialPort != null) {
            log.info("==========================port close: {}=========================", serialPort.getName());
            serialPort.close();
        }
    }

    /**
     * Send data on com port
     *
     * @param serialPort
     * @param data
     */
    public static boolean sendToPort(SerialPort serialPort, byte[] data) {
        OutputStream out = null;
        try {
            out = serialPort.getOutputStream();
            out.write(data);
            out.flush();
        } catch (IOException e) {
            log.error("Error occurs in sending data. port: {}, msg: {}",
                    serialPort.getName(), e.getMessage());
            return false;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                log.error("Error occurs in closing stream. port: {}, msg: {}",
                        serialPort.getName(), e.getMessage());
            }
        }
        return true;
    }

    /**
     * Read data from com port
     *
     * @param serialPort
     * @return read bytes
     */
    public static byte[] readFromPort(SerialPort serialPort) throws IOException {
        InputStream in = null;
        byte[] bytes = new byte[] {};
        try {
            in = serialPort.getInputStream();
            int buffLen = in.available();
            while (buffLen != 0) {
                byte[] buf = new byte[buffLen];
                in.read(buf);
                bytes = ByteUtil.concat(bytes, buf);
                buffLen = in.available();
            }
        } catch (IOException e) {
            throw e;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                log.error("Error occurs in closing stream. port: {}, msg: {}",
                        serialPort.getName(), e.getMessage());
            }
        }
        return bytes;
    }


    /**
     * Add listener
     *
     * @param port
     * @param listener
     */
    public static void addListener(SerialPort port, SerialPortEventListener listener) {
        try {
            port.addEventListener(listener);
            port.notifyOnDataAvailable(true);
            port.notifyOnBreakInterrupt(true);
        } catch (TooManyListenersException e) {
            log.error("Too many listeners.");
            e.printStackTrace();
        }
    }

    /**
     * Remove listener
     *
     * @param port
     * @param listener
     */
    public static void removeListener(SerialPort port, SerialPortEventListener listener) {
        port.removeEventListener();
    }

}
